/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package fauxman.model;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.RectangularShape;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Timer;
import fauxman.DynamicElements.BlinkyGhost;
import fauxman.DynamicElements.Bonus;
import fauxman.DynamicElements.ClydeGhost;
import fauxman.DynamicElements.FauxMan;
import fauxman.DynamicElements.Food;
import fauxman.DynamicElements.Ghost;
import fauxman.DynamicElements.InkyGhost;
import fauxman.DynamicElements.Pellet;
import fauxman.DynamicElements.PinkyGhost;
import fauxman.Enums.GameElementName;
import fauxman.Enums.StateType;
import fauxman.StaticElements.Blank;
import fauxman.StaticElements.GhostHouseArea;
import fauxman.StaticElements.PlayableArea;
import fauxman.StaticElements.Wormhole;
import fauxman.TimerTasks.LeavingHouseTask;
import fauxman.TimerTasks.ModeChangeTask;
import fauxman.TimerTasks.VulnerableGhostTask;
import fauxman.interfaces.DynamicElement;
import fauxman.interfaces.GameElement;
import fauxman.interfaces.MoveableElement;
import fauxman.states.EatenGhostState;
import fauxman.states.GhostHouseState;
import fauxman.states.GhostState;
import fauxman.states.VulnerableGhostState;
import fauxman.utilities.ActionUtil;


public class WorldModel {

	//Internal representation
	private Rectangle [] boundary; //The rectangles used to represent the boundaries
	private Collection<PlayableArea> playableArea;
	private Collection<GhostHouseArea> ghostHouseArea; //TODO:GhostHouseArea not needed
	private Collection<PlayableArea> pManCorneringArea;
	
	private Collection<Wormhole> wormholes;
	private Collection<DynamicElement> dynamicElements;
	private Collection<MoveableElement> moveableElements;
	private Collection<Ghost> ghosts;
	private ArrayList<ArrayList<ArrayList<GameElement>>> discreteBoard;
	private ArrayList<ArrayList<ArrayList<GameElementName>>> intersections;

	public static final int PRESENTATION_SCALER = 3;
	public static final int PIXELS_PER_UNIT = 8 * PRESENTATION_SCALER;	// 24 because the dossier says the original was 8, and we wanted a little bigger
	private static final int DISCRETE_BOARD_ROWS = 31;
	private static final int DISCRETE_BOARD_COLS = 28; //includes wormholes
	
	//Outer Walls
	private int OUTLINE_WIDTH = 2; //The wall consists of 2 blue line and 1 black line each 2 pixels 
	private int numWallUnits = 55;
	private int OUTER_WALL_MIDDLE_X_ADJUSTMENT = (PIXELS_PER_UNIT - OUTLINE_WIDTH * 3 ) / 2; //want to put the wall in the middle of the unit
	private int OUTER_WALL_MIDDLE_Y_ADJUSTMENT = (PIXELS_PER_UNIT - OUTLINE_WIDTH * 3 ) / 2; //Want to put the wall in the middle of the unit
	
	public int anchorX = 0;
	private int anchorY = 0;

	//Food
	private int FOOD_HEIGHT = 4;
	private int FOOD_WIDTH = 4;
	private int FOOD_MIDDLE_X_ADJUSTMENT = (PIXELS_PER_UNIT - FOOD_WIDTH) / 2;
	private int FOOD_MIDDLE_Y_ADJUSTMENT = (PIXELS_PER_UNIT - FOOD_HEIGHT) / 2;

	//Pellet
	private int PELLET_HEIGHT = 18;
	private int PELLET_WIDTH = 18;
	private int PELLET_MIDDLE_Y_ADJUSTMENT = (PIXELS_PER_UNIT - PELLET_WIDTH) /2;
	private int PELLET_MIDDLE_X_ADJUSTMENT = (PIXELS_PER_UNIT - PELLET_HEIGHT) /2;
	
	//Ghost
	private int GHOST_WIDTH = PIXELS_PER_UNIT;
	private int GHOST_HEIGHT = PIXELS_PER_UNIT;
	private int GHOST_MIDDLE_Y_ADJUSTMENT = (PIXELS_PER_UNIT - GHOST_WIDTH) /2;
	private int GHOST_MIDDLE_X_ADJUSTMENT = (PIXELS_PER_UNIT - GHOST_HEIGHT) /2;
	private int GHOST_HOUSE_AREA_X_ADJUSTMENT = (PIXELS_PER_UNIT/8)*7;
	private int GHOST_HOUSE_AREA_Y_ADJUSTMENT = (PIXELS_PER_UNIT/8)*4;
	private BlinkyGhost blinky;
	
	//Faux Man
	private int FAUXMAN_WIDTH = PIXELS_PER_UNIT;
	private int FAUXMAN_HEIGHT = PIXELS_PER_UNIT;
	private int FAUXMAN_MIDDLE_Y_ADJUSTMENT = (PIXELS_PER_UNIT - GHOST_WIDTH) /2;
	private int FAUXMAN_MIDDLE_X_ADJUSTMENT = (PIXELS_PER_UNIT - GHOST_HEIGHT) /2;
	private FauxMan fMan;//, fMan2;
	
	//Bonus
	private int BONUS_WIDTH = PIXELS_PER_UNIT;
	private int BONUS_HEIGHT = PIXELS_PER_UNIT;
	private int BONUS_MIDDLE_Y_ADJUSTMENT = (PIXELS_PER_UNIT - GHOST_WIDTH) /2;
	private int BONUS_MIDDLE_X_ADJUSTMENT = (PIXELS_PER_UNIT - GHOST_HEIGHT) /2;
	
	//Playable Area
	private int AREA_MIDDLE_X_ADJUSTMENT = (FAUXMAN_WIDTH / 8)*3;
	private int AREA_MIDDLE_Y_ADJUSTMENT = (FAUXMAN_HEIGHT / 8)*4;
	private int AREA_WIDTH = FAUXMAN_WIDTH;
	private int AREA_HEIGHT = FAUXMAN_HEIGHT;
	private int AREA_X_THICKNESS = FAUXMAN_WIDTH / 8;
	private int AREA_Y_THICKNESS = FAUXMAN_HEIGHT / 8;
	private int AREA_DEAD_SPACE = PIXELS_PER_UNIT - AREA_X_THICKNESS;
	
	//Game info
	private int totalScore;
	private int FOOD_POINTS = 10;
	private int BONUS_POINTS = 100;
	private int GHOST_POINTS = 200;
	private DynamicElement ateDE;
	private int PELLETS_ACTIVE;
	private boolean pause = false;
	
	//Scatter to Chase Timer
	/*
	Mode		Level 1		Level 2-4	Level 5+
	scatter		7			7			5
	chase		20			20			20
	scatter		7			7			5
	chase		20			20			20
	scatter		5			5			5
	chase		20			1033		1037
	scatter		5			1/60		1/60
	chase		indefinite	indefinite	indefinite
	*/
	private boolean ghostStateScatter;
	private int modeNumber;
	public static final long INDEFINITE = 999999999999999999l;
	//private long[] modeSeconds = {7000,20000,7000,20000,5000,20000,5000,INDEFINITE};
	private long[] modeSeconds = {7000,INDEFINITE};
	//private long[] modeSeconds = {2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,INDEFINITE};

	
	//Vulnerable Timer
	Timer vTm;
	private long VULNERABLE_TIME = 8000;
	ModeChangeTask modeChangeTask;
	
	
	//Debug Timer
	private boolean ghostStateLeaving;
	private long leaveSeconds = 5000;
	//private long leaveSeconds = INDEFINITE;
	
	
	public int getOutlineWidth() {
		return OUTLINE_WIDTH;
	}

	public WorldModel(){
		setupGameInfo();
		createBoundaries();
		createStaticElements();
		createDynamicElements();
		createMoveableElements();
		createDiscreteRepresentation();
	}

	private void createDiscreteRepresentation() {
		
		discreteBoard = new ArrayList<ArrayList<ArrayList<GameElement>>>();
		Rectangle tile = new Rectangle();
		intersections = new ArrayList<ArrayList<ArrayList<GameElementName>>>(); 
		
		for(int row =0; row < DISCRETE_BOARD_ROWS; row++){ 
			discreteBoard.add(new ArrayList<ArrayList<GameElement>>()); //initialize the Y
			intersections.add(new ArrayList<ArrayList<GameElementName>>()); //initialize the Y
			for(int col =0; col < DISCRETE_BOARD_COLS; col++){
				discreteBoard.get(row).add(new ArrayList<GameElement>()); //initialize the X
				intersections.get(row).add(new ArrayList<GameElementName>()); //initialize the X
				tile.setBounds(anchorX + PIXELS_PER_UNIT*col, anchorY + PIXELS_PER_UNIT*row, PIXELS_PER_UNIT, PIXELS_PER_UNIT);
				for(PlayableArea pa:playableArea){
					if(Hitbox.hit(pa.getConHitbox(),tile)){
						discreteBoard.get(row).get(col).add(pa);
					}
				}
				// Adds PlayableArea intersections to the array
				if(discreteBoard.get(row).get(col).size() > 1){
					intersections.get(row).get(col).add(GameElementName.PLAYABLE_AREA);
				}
				
				for(GhostHouseArea ga:ghostHouseArea){
					if(Hitbox.hit(ga.getConHitbox(),tile)){
						discreteBoard.get(row).get(col).add(ga);
					}
				}
				// Adds GhostHouseArea intersections to the array IF it is not already a PlayableArea intersection
				// This includes the special case at tile (13,10) which is NOT a GhostHouseArea intersection  
				if(((intersections.get(row).get(col).size() > 0 && intersections.get(row).get(col).get(0) != GameElementName.PLAYABLE_AREA )|| intersections.get(row).get(col).size() == 0 ) && discreteBoard.get(row).get(col).size() > 1){
					int ghostHouseAreaCount = 0;
					for(GameElement ge: discreteBoard.get(row).get(col)){
						if(ge.getEnum() == GameElementName.GHOST_HOUSE_AREA){
							ghostHouseAreaCount++;
						}
					}
					if(ghostHouseAreaCount > 1){
						intersections.get(row).get(col).add(GameElementName.GHOST_HOUSE_AREA);
					}
				}
				
				for(DynamicElement me:moveableElements){
					if(Hitbox.hit(me.getConHitbox(),tile)){
						discreteBoard.get(row).get(col).add(me);
					}
				}
				for(Wormhole wh: wormholes){
					if(Hitbox.hit(wh.getConHitbox(), tile)){
						discreteBoard.get(row).get(col).add(wh);
					}
				}
				if(discreteBoard.get(row).get(col).size() == 0){
					discreteBoard.get(row).get(col).add(Blank.getInstance());
				}
			}
		}
		intersections.get(11).get(12).add(GameElementName.NO_UP_INTERSECTION);
		intersections.get(11).get(15).add(GameElementName.NO_UP_INTERSECTION);
		intersections.get(23).get(12).add(GameElementName.NO_UP_INTERSECTION);
		intersections.get(23).get(15).add(GameElementName.NO_UP_INTERSECTION);
		System.out.println(" ");
		for(int col=0; col < DISCRETE_BOARD_COLS; col++){
			System.out.print(col);
		}
		System.out.println();
		for(int row =0; row < DISCRETE_BOARD_ROWS; row++){
			System.out.print(row);
			for(int col=0; col < DISCRETE_BOARD_COLS; col++){
				System.out.print(col+intersections.get(row).get(col).toString());
			}
			System.out.println();
		}
		for(int row =0; row < DISCRETE_BOARD_ROWS; row++){
			for(int col=0; col < DISCRETE_BOARD_COLS; col++){
				System.out.print(discreteBoard.get(row).get(col));
			}
			System.out.println();
		}
	}
	
	public boolean isPlayableAreaIntersection(int disX, int disY){
		for(GameElementName gEN:intersections.get(disY).get(disX)){
			if(GameElementName.PLAYABLE_AREA == gEN){
				return true;
			}
		}
		return false;
	}
	
	public boolean isGhostHouseAreaIntersection(int disX, int disY){
		for(GameElementName gEN:intersections.get(disY).get(disX)){
			if(GameElementName.GHOST_HOUSE_AREA == gEN){
				return true;
			}
		}
		return false;
	}
	
	public boolean isGhostNoUpIntersection(int disX, int disY) {
		for(GameElementName gEN:intersections.get(disY).get(disX)){
			if(GameElementName.NO_UP_INTERSECTION == gEN){
				return true;
			}
		}
		return false;
	}
	
	/*
	 * At an intersection it will return a list of points of playable areas N, W, S and E of the given x and y
	 */
	public ArrayList<int[]> validPlayableAreaIntersectionDirections(int disX, int disY){  
		ArrayList<int[]> choices = new ArrayList<int[]>(); 
		if(!collectionContainsEnum(discreteBoard.get(disY-1).get(disX),GameElementName.BLANK)){	// Up
			//choices.add(new Point(disX,disY-1));
			choices.add(ActionUtil.NORTH);
		}
		if(!collectionContainsEnum(discreteBoard.get(disY).get(disX-1),GameElementName.BLANK)){	// Left
			//choices.add(new Point(disX-1,disY));
			choices.add(ActionUtil.WEST);
		}
		if(!collectionContainsEnum(discreteBoard.get(disY+1).get(disX),GameElementName.BLANK)){	// Down
			//choices.add(new Point(disX,disY+1));
			choices.add(ActionUtil.SOUTH);
		}
		if(!collectionContainsEnum(discreteBoard.get(disY).get(disX+1),GameElementName.BLANK)){	// Right
			//choices.add(new Point(disX+1,disY));
			choices.add(ActionUtil.EAST);
		}
		return choices;
	}
	
	public ArrayList<int[]> validGhostHouseAreaIntersectionDirections(int disX, int disY) {
		ArrayList<int[]> choices = new ArrayList<int[]>(); 
		if(!collectionContainsEnum(discreteBoard.get(disY-1).get(disX),GameElementName.BLANK)){	// Up
			//choices.add(new Point(disX,disY-1));
			choices.add(ActionUtil.NORTH);
		}
		if(!collectionContainsEnum(discreteBoard.get(disY).get(disX-1),GameElementName.BLANK)){	// Left
			//choices.add(new Point(disX-1,disY));
			choices.add(ActionUtil.WEST);
		}
		if(!collectionContainsEnum(discreteBoard.get(disY+1).get(disX),GameElementName.BLANK)){	// Down
			//choices.add(new Point(disX,disY+1));
			choices.add(ActionUtil.SOUTH);
		}
		if(!collectionContainsEnum(discreteBoard.get(disY).get(disX+1),GameElementName.BLANK)){	// Right
			//choices.add(new Point(disX+1,disY));
			choices.add(ActionUtil.EAST);
		}
		return choices;
	}
	
	private boolean collectionContainsEnum(Collection<GameElement> dBoard, GameElementName... enums){
		for(GameElement ge: dBoard){
			for(GameElementName e: enums){
				if(ge.getEnum() == e){
					return true;
				}
			}
		}
		return false;
	}
	
	public int getPixelsPerUnit(){
		return PIXELS_PER_UNIT;
	}
	
	public void drawGrid(Graphics g){
		g.setColor(Color.ORANGE);
		for(int row =0; row < DISCRETE_BOARD_ROWS; row++)
			for(int col =0; col < DISCRETE_BOARD_COLS; col++)
				g.drawRect(anchorX + PIXELS_PER_UNIT*col, anchorY + PIXELS_PER_UNIT*row, PIXELS_PER_UNIT, PIXELS_PER_UNIT);
	}
	
	private void setupGameInfo() {
		totalScore = 0;
		vTm = new Timer();
		
		modeNumber = 0;
		modeChangeTask = new ModeChangeTask(this);
		vTm.schedule(new ModeChangeTask(this), getCurrentModeSeconds());
		ghostStateScatter = true;
		PELLETS_ACTIVE = 0;
		
		vTm.schedule(new LeavingHouseTask(this), leaveSeconds);
	}

	private void createStaticElements() {
		playableArea = new ArrayList<PlayableArea>();
		ghostHouseArea = new ArrayList<GhostHouseArea>();
		pManCorneringArea = new ArrayList<PlayableArea>();
		wormholes = new ArrayList<Wormhole>();
		
		//General Playable Area
		createPlayableArea(1,1,12,1);
		createPlayableArea(1,20,12,1);
		createPlayableArea(6,1,1,26);
		createPlayableArea(1,1,1,8);
		createPlayableArea(12,1,1,5);
		createPlayableArea(15,1,1,5);
		createPlayableArea(21,1,1,26);
		createPlayableArea(1,5,26,1);
		createPlayableArea(15,1,12,1);
		createPlayableArea(26,1,1,8);
		createPlayableArea(1,8,6,1);
		createPlayableArea(9,5,1,4);
		createPlayableArea(18,5,1,4);
		createPlayableArea(15,8,4,1);
		createPlayableArea(9,8,4,1);
		createPlayableArea(21,8,6,1);
		createPlayableArea(0,14,10,1);
		createPlayableArea(12,8,1,4);
		createPlayableArea(15,8,1,4);
		createPlayableArea(9,11,10,1);
		createPlayableArea(9,11,1,10);
		createPlayableArea(9,17,10,1);
		createPlayableArea(18,14,10,1);
		createPlayableArea(18,11,1,10);
		createPlayableArea(1,20,1,4);
		createPlayableArea(1,23,3,1);
		createPlayableArea(3,23,1,4);
		createPlayableArea(1,26,6,1);
		createPlayableArea(1,26,1,4);
		createPlayableArea(12,20,1,4);
		createPlayableArea(6,23,16,1);
		createPlayableArea(9,23,1,4);
		createPlayableArea(18,23,1,4);
		createPlayableArea(9,26,4,1);
		createPlayableArea(12,26,1,4);
		createPlayableArea(15,26,4,1);
		createPlayableArea(15,26,1,4);
		createPlayableArea(1,29,26,1);
		createPlayableArea(26,26,1,4);
		createPlayableArea(26,20,1,4);
		createPlayableArea(24,23,1,4);
		createPlayableArea(15,20,1,4);
		createPlayableArea(15,20,12,1);
		createPlayableArea(24,23,3,1);
		createPlayableArea(21,26,6,1);
		
		//Ghost House Area
		createGhostHouseArea(13,11,1,5);
		createGhostHouseArea(12,11,3,1);
		createGhostHouseArea(11,13,1,3);
		createGhostHouseArea(15,13,1,3);
		createGhostHouseArea(11,14,6,1);
		
		//Cornering Areas
		createCorneringArea(pManCorneringArea);
		
		//Wormholes
		wormholes.add(new Wormhole(AREA_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT * 27 + (AREA_X_THICKNESS), AREA_MIDDLE_Y_ADJUSTMENT + anchorY + PIXELS_PER_UNIT * 14, (AREA_X_THICKNESS), (AREA_Y_THICKNESS), FAUXMAN_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT * 0, FAUXMAN_MIDDLE_Y_ADJUSTMENT + anchorY + PIXELS_PER_UNIT * 14, 0, 14));
		wormholes.add(new Wormhole(AREA_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT * 0 - (AREA_X_THICKNESS), AREA_MIDDLE_Y_ADJUSTMENT + anchorY + PIXELS_PER_UNIT * 14, (AREA_X_THICKNESS), (AREA_Y_THICKNESS), FAUXMAN_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT * 27, FAUXMAN_MIDDLE_Y_ADJUSTMENT + anchorY + PIXELS_PER_UNIT * 14, 27, 14));
	}
	
	private void createCorneringArea(Collection<PlayableArea> corneringArea) {
		/*ArrayList<ArrayList<Character>> cornerMap = new ArrayList<ArrayList<Character>>();
		for(int row = 0;  row< DISCRETE_BOARD_ROWS; row ++)
		{
			cornerMap.add(new ArrayList<Character>(DISCRETE_BOARD_COLS));
		}*/
//		corneringArea.add(new PlayableArea(0, 0, 672,744));
		
		//if you can go SW from North, you need to add Q1 to the West discrete position
		//if you can go SW from the East, you need to add Q1 to the South discrete position
		
		makeCorner( 0, "", corneringArea);
		makeCorner( 1, "0,c4,2,3,4,5,c34,7,8,9,10,11,c3,13,14,c4,16,17,18,19,20,c34,22,23,24,25,c3",corneringArea);
		makeCorner( 2, "",corneringArea);
		makeCorner( 3, "",corneringArea);
		makeCorner( 4, "",corneringArea);
		makeCorner( 5, "0,c14,2,3,4,5,c1234,7,8,c34,10,11,c12,13,14,c12,16,17,c34,19,20,c1234,22,23,24,25,c23",corneringArea);
		makeCorner( 6, "",corneringArea);
		makeCorner( 7, "",corneringArea);
		makeCorner( 8, "0,c1,2,3,4,5,c23,7,8,c1,10,11,c3,13,14,c4,16,17,c2,19,20,c14,22,23,24,25,c2",corneringArea);
		makeCorner( 9, "",corneringArea);
		makeCorner( 10, "",corneringArea);
		makeCorner( 11, "0,1,2,3,4,5,6,7,8,c4,10,11,c12,13,14,c12,16,17,c3,19,20,21,22,23,24,25,26",corneringArea);
		makeCorner( 12, "",corneringArea);
		makeCorner( 13, "",corneringArea);
		makeCorner( 14, "0,1,2,3,4,5,c1234,7,8,c23,10,11,12,13,14,15,16,17,c14,19,20,c1234,22,23,24,25,26",corneringArea);
		makeCorner( 15, "",corneringArea);
		makeCorner( 16, "",corneringArea);
		makeCorner( 17, "0,1,2,3,4,5,6,7,8,c14,10,11,12,13,14,15,16,17,c23,19,20,21,22,23,24,25,26",corneringArea);
		makeCorner( 18, "",corneringArea);
		makeCorner( 19, "",corneringArea);
		makeCorner( 20, "0,c4,2,3,4,5,c1234,7,8,c12,10,11,c3,13,14,c4,16,17,c12,19,20,c1234,22,23,24,25,c3",corneringArea);
		makeCorner( 21, "",corneringArea);
		makeCorner( 22, "",corneringArea);
		makeCorner( 23, "0,c1,2,c3,4,5,c14,7,8,c34,10,11,c12,13,14,c12,16,17,c34,19,20,c23,22,23,c4,25,c2",corneringArea);
		makeCorner( 24, "",corneringArea);
		makeCorner( 25, "",corneringArea);
		makeCorner( 26, "0,c4,2,c12,4,5,c2,7,8,c1,10,11,c3,13,14,c4,16,17,c2,19,20,c1,22,23,c12,25,c3",corneringArea);
		makeCorner( 27, "",corneringArea);
		makeCorner( 28, "",corneringArea);
		makeCorner( 29, "0,c1,2,3,4,5,6,7,8,9,10,11,c12,13,14,c12,16,17,18,19,20,21,22,23,24,25,c2",corneringArea);
		
	}

	private void makeCorner(int row, String metadata, Collection<PlayableArea> corneringArea) {
		int column = 0;
		for(int index = 0; index < metadata.length();index++)
		{			
			switch(metadata.charAt(index))
			{
				case ',': 
				{
					column++;
					break;
				}
				case 'c':
				{
					//Rectangle rec = new Rectangle();
					index++;
					while(index < metadata.length() && metadata.charAt(index)!=',')
					{
						int quadrant = Character.getNumericValue(metadata.charAt(index));
						corneringArea.add(new PlayableArea(createCorneringAreaQuadrant(anchorX + PIXELS_PER_UNIT*column, anchorY + PIXELS_PER_UNIT*row, quadrant)));		
						index++;
					}
					column++;
					//corneringArea.add(new PlayableArea(rec));
					break;
				}
				default:
				{
					;//If its a column id then dont do anything
				}
			}
		}
		
	}
	
	//Row 1
	private static final int[] QUADRANT_POINT_1 = {0 - PRESENTATION_SCALER, 0}; //For X added (-1) for the extra pixel needed for S to W turns
	private static final int[] QUADRANT_POINT_2 = {3*PRESENTATION_SCALER, 0};
	private static final int[] QUADRANT_POINT_3 = {8*PRESENTATION_SCALER, 0};
	
	//Row 2
	private static final int[] QUADRANT_POINT_4 = {0, 4*PRESENTATION_SCALER};
	private static final int[] QUADRANT_POINT_5 = {3*PRESENTATION_SCALER, 4*PRESENTATION_SCALER}; //For Y added (+1) for extra pixel needed for W to S turns
	private static final int[] QUADRANT_POINT_6 = {8*PRESENTATION_SCALER, 4*PRESENTATION_SCALER};
	
	//Row 3
	private static final int[] QUADRANT_POINT_7 = {0, 8*PRESENTATION_SCALER};
	private static final int[] QUADRANT_POINT_8 = {3*PRESENTATION_SCALER, 8*PRESENTATION_SCALER};
	private static final int[] QUADRANT_POINT_9 = {8*PRESENTATION_SCALER, 8*PRESENTATION_SCALER};
	
	
	private Rectangle createCorneringAreaQuadrant(int columnOffset, int rowOffset, int quadrant) {
		Rectangle rec = new Rectangle();
		int leftX = 0;
		int	leftY = 0; 
		int	width = 0; 
		int	height = 0;
		switch(quadrant)
		{
			case 1:
			{
				leftX = QUADRANT_POINT_2[0];
				leftY = QUADRANT_POINT_2[1];
				width = 5*PRESENTATION_SCALER;
				height = 5*PRESENTATION_SCALER;
				break;
			}
			case 2:
			{
				leftX = QUADRANT_POINT_1[0];
				leftY = QUADRANT_POINT_1[1];
				width = 5*PRESENTATION_SCALER; //Added 1 extra for the extra pixel needed for S to W turns
				height = 5*PRESENTATION_SCALER;
				break;
			}
			case 3:
			{
				leftX = QUADRANT_POINT_4[0];
				leftY = QUADRANT_POINT_4[1];
				width = 4*PRESENTATION_SCALER;
				height = 4*PRESENTATION_SCALER;
				break;
			}
			case 4:
			{
				leftX = QUADRANT_POINT_5[0];
				leftY = QUADRANT_POINT_5[1];
				width = 5*PRESENTATION_SCALER;
				height = 5*PRESENTATION_SCALER; //Added 1 extra for the extra pixel needed for W to S turns
				break;
			}
		}
		return new Rectangle(columnOffset + leftX, rowOffset + leftY, width, height);
	}

	private void createPlayableArea(int start_col, int start_row, int width, int height){
		playableArea.add(new PlayableArea(AREA_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT*start_col, AREA_MIDDLE_Y_ADJUSTMENT + anchorY + PIXELS_PER_UNIT*start_row, (AREA_X_THICKNESS*width)+(AREA_DEAD_SPACE*(width-1)), (AREA_Y_THICKNESS*height)+(AREA_DEAD_SPACE*(height-1))));
	}
	
	private void createGhostHouseArea(int start_col, int start_row, int width, int height){
		ghostHouseArea.add(new GhostHouseArea(GHOST_HOUSE_AREA_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT*start_col, GHOST_HOUSE_AREA_Y_ADJUSTMENT + anchorY + PIXELS_PER_UNIT*start_row, (AREA_X_THICKNESS*width)+(AREA_DEAD_SPACE*(width-1)), (AREA_Y_THICKNESS*height)+(AREA_DEAD_SPACE*(height-1))));
	}
	
	private void createMoveableElements(){
		moveableElements = new ArrayList<MoveableElement>();
		ghosts = new ArrayList<Ghost>();
		
		//Ghosts
		Ghost pinky = new PinkyGhost(GHOST_HOUSE_AREA_X_ADJUSTMENT - AREA_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT*13, anchorY + PIXELS_PER_UNIT*14, 13, 14, GHOST_WIDTH, GHOST_HEIGHT, this);
		Ghost inky = new InkyGhost(GHOST_HOUSE_AREA_X_ADJUSTMENT - AREA_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT*11, anchorY + PIXELS_PER_UNIT*14, 11, 14,  GHOST_WIDTH, GHOST_HEIGHT, this);
		Ghost clyde = new ClydeGhost(GHOST_HOUSE_AREA_X_ADJUSTMENT - AREA_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT*15, anchorY + PIXELS_PER_UNIT*14, 15, 14,  GHOST_WIDTH, GHOST_HEIGHT, this);
		blinky = new BlinkyGhost(anchorX + PIXELS_PER_UNIT*13, anchorY + PIXELS_PER_UNIT*11, 13, 11,  GHOST_WIDTH, GHOST_HEIGHT, this);

		//PacMan
		fMan = new FauxMan(anchorX + PIXELS_PER_UNIT*13, anchorY + PIXELS_PER_UNIT*23, 13, 23, FAUXMAN_WIDTH, FAUXMAN_HEIGHT, this);
		
		//Bonus
		Bonus bo = new Bonus(anchorX + PIXELS_PER_UNIT*13, anchorY + PIXELS_PER_UNIT*17,  13, 17, BONUS_WIDTH, BONUS_HEIGHT, this);
		
		dynamicElements.add(pinky);
		dynamicElements.add(inky);
		dynamicElements.add(clyde);
		dynamicElements.add(blinky);
		dynamicElements.add(bo);
		dynamicElements.add(fMan);
		
		moveableElements.add(pinky);
		moveableElements.add(inky);
		moveableElements.add(clyde);
		moveableElements.add(blinky);
		moveableElements.add(bo);
		moveableElements.add(fMan);
		
		ghosts.add(pinky);
		ghosts.add(inky);
		ghosts.add(clyde);
		ghosts.add(blinky);
	}
	
	private void makeFood(int row, String food){
		for(int col = 0; col < food.length(); col++){
			if(food.charAt(col) == '.'){	// food
				dynamicElements.add(new Food(FOOD_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT*col, FOOD_MIDDLE_Y_ADJUSTMENT + anchorY + PIXELS_PER_UNIT*row, col, row, FOOD_WIDTH, FOOD_HEIGHT));
			}else if(food.charAt(col) == 'p'){	// pellets
				dynamicElements.add(new Pellet(PELLET_MIDDLE_X_ADJUSTMENT + anchorX + PIXELS_PER_UNIT*col, PELLET_MIDDLE_Y_ADJUSTMENT + anchorY + PIXELS_PER_UNIT*row, col,row, PELLET_HEIGHT, PELLET_HEIGHT));
			}
		}
	}
	
	private void createDynamicElements(){
		dynamicElements = new ArrayList<DynamicElement>();
		
		//TODO: Make this better
		makeFood( 1, " ............  ............ ");
		makeFood( 2, " .    .     .  .     .    . ");
		makeFood( 3, " p    .     .  .     .    p ");
		makeFood( 4, " .    .     .  .     .    . ");
		makeFood( 5, " .......................... ");
		makeFood( 6, " .    .  .        .  .    . ");
		makeFood( 7, " .    .  .        .  .    . ");
		makeFood( 8, " ......  ....  ....  ...... ");
		makeFood( 9, "      .              .      ");
		makeFood(10, "      .              .      ");
		makeFood(11, "      .              .      ");
		makeFood(12, "      .              .      ");
		makeFood(13, "      .              .      ");
		makeFood(14, "      .              .      ");
		makeFood(15, "      .              .      ");
		makeFood(16, "      .              .      ");
		makeFood(17, "      .              .      ");
		makeFood(18, "      .              .      ");
		makeFood(19, "      .              .      ");
		makeFood(20, " ............  ............ ");
		makeFood(21, " .    .     .  .     .    . ");
		makeFood(22, " p    .     .  .     .    p ");
		makeFood(23, " ...  .......  .......  ... ");
		makeFood(24, "   .  .  .        .  .  .   ");
		makeFood(25, "   .  .  .        .  .  .   ");
		makeFood(26, " ......  ....  ....  ...... ");
		makeFood(27, " .          .  .          . ");
		makeFood(28, " .          .  .          . ");
		makeFood(29, " .......................... ");
	}

	private void createBoundaries(){
	
		boundary = new Rectangle[numWallUnits];
		
		//Outer Walls
		boundary[0] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 27 + OUTER_WALL_MIDDLE_X_ADJUSTMENT, OUTLINE_WIDTH * 3); //O5
		boundary[1] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, OUTLINE_WIDTH * 3, PIXELS_PER_UNIT * 9 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT); //O6
		boundary[2] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*27, anchorY + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, OUTLINE_WIDTH * 3, PIXELS_PER_UNIT * 9 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT  ); //O4		
		boundary[3] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + PIXELS_PER_UNIT * 9 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 5 + OUTER_WALL_MIDDLE_X_ADJUSTMENT,  OUTLINE_WIDTH * 3); //O7
		boundary[4] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*22, anchorY + PIXELS_PER_UNIT * 9 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 5 + OUTER_WALL_MIDDLE_X_ADJUSTMENT,  OUTLINE_WIDTH * 3); //O3
		boundary[5] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*5, anchorY + PIXELS_PER_UNIT * 9 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, OUTLINE_WIDTH * 3, PIXELS_PER_UNIT * 4 + OUTER_WALL_MIDDLE_X_ADJUSTMENT  ); //O8
		boundary[6] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*22, anchorY + PIXELS_PER_UNIT * 9 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, OUTLINE_WIDTH * 3, PIXELS_PER_UNIT * 4 + OUTER_WALL_MIDDLE_X_ADJUSTMENT  ); //O2
		boundary[7] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + PIXELS_PER_UNIT * 13 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 5 + OUTER_WALL_MIDDLE_X_ADJUSTMENT, OUTLINE_WIDTH * 3   ); //O9
		boundary[8] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*22, anchorY + PIXELS_PER_UNIT * 13 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 5 + OUTER_WALL_MIDDLE_X_ADJUSTMENT, OUTLINE_WIDTH * 3   ); //O1
		boundary[9] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + PIXELS_PER_UNIT * 15 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 5 + OUTER_WALL_MIDDLE_X_ADJUSTMENT, OUTLINE_WIDTH * 3   ); //O10
		boundary[10] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*22, anchorY + PIXELS_PER_UNIT * 15 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 5 + OUTER_WALL_MIDDLE_X_ADJUSTMENT, OUTLINE_WIDTH * 3   ); //O18
		boundary[11] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*5, anchorY + PIXELS_PER_UNIT * 15 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, OUTLINE_WIDTH * 3, PIXELS_PER_UNIT * 4 + OUTER_WALL_MIDDLE_X_ADJUSTMENT  ); //O11
		boundary[12] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*22, anchorY + PIXELS_PER_UNIT * 15 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, OUTLINE_WIDTH * 3, PIXELS_PER_UNIT * 4 + OUTER_WALL_MIDDLE_X_ADJUSTMENT  ); //O17
		boundary[13] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + PIXELS_PER_UNIT * 19 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 5 + OUTER_WALL_MIDDLE_X_ADJUSTMENT, OUTLINE_WIDTH * 3   ); //O12
		boundary[14] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*22, anchorY + PIXELS_PER_UNIT * 19 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT, PIXELS_PER_UNIT * 5 + OUTER_WALL_MIDDLE_X_ADJUSTMENT, OUTLINE_WIDTH * 3   ); //O16
		boundary[15] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + OUTER_WALL_MIDDLE_Y_ADJUSTMENT + PIXELS_PER_UNIT*19, OUTLINE_WIDTH * 3, PIXELS_PER_UNIT * 11 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT); //O13
		boundary[16] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT + PIXELS_PER_UNIT*27, anchorY + OUTER_WALL_MIDDLE_Y_ADJUSTMENT + PIXELS_PER_UNIT*19, OUTLINE_WIDTH * 3, PIXELS_PER_UNIT * 11 + OUTER_WALL_MIDDLE_Y_ADJUSTMENT  ); //O15	
		boundary[17] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + OUTER_WALL_MIDDLE_Y_ADJUSTMENT + PIXELS_PER_UNIT * 30, PIXELS_PER_UNIT * 27 + OUTER_WALL_MIDDLE_X_ADJUSTMENT, OUTLINE_WIDTH * 3); //O14
	
		//Inner Wall
		boundary[18] = new Rectangle(anchorX + PIXELS_PER_UNIT * 2, anchorY +  PIXELS_PER_UNIT * 2, PIXELS_PER_UNIT * 4 ,  PIXELS_PER_UNIT * 3 ); //I10
		boundary[19] = new Rectangle(anchorX + PIXELS_PER_UNIT * 7, anchorY + PIXELS_PER_UNIT * 2, PIXELS_PER_UNIT * 5 ,  PIXELS_PER_UNIT * 3 ); //I9
		boundary[20] = new Rectangle(anchorX + PIXELS_PER_UNIT * 2, anchorY  + PIXELS_PER_UNIT * 6, PIXELS_PER_UNIT * 4 ,  PIXELS_PER_UNIT * 2 ); //I11
		boundary[21] = new Rectangle(anchorX + PIXELS_PER_UNIT * 7, anchorY + PIXELS_PER_UNIT * 6, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 8 ); //I12
		boundary[22] = new Rectangle(anchorX + PIXELS_PER_UNIT * 7, anchorY + PIXELS_PER_UNIT * 9, PIXELS_PER_UNIT * 5 ,  PIXELS_PER_UNIT * 2 ); //I13
		boundary[23] = new Rectangle(anchorX + PIXELS_PER_UNIT * 13, anchorY + OUTER_WALL_MIDDLE_Y_ADJUSTMENT , PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 - OUTER_WALL_MIDDLE_Y_ADJUSTMENT ); //I8 //Connects to Outer wall
		boundary[24] = new Rectangle(anchorX + PIXELS_PER_UNIT * 16, anchorY +  PIXELS_PER_UNIT * 2 , PIXELS_PER_UNIT * 5 ,  PIXELS_PER_UNIT * 3 ); //I5
		boundary[25] = new Rectangle(anchorX + PIXELS_PER_UNIT * 22, anchorY +  PIXELS_PER_UNIT * 2, PIXELS_PER_UNIT * 4 ,  PIXELS_PER_UNIT * 3 ); //I4
		boundary[26] = new Rectangle(anchorX + PIXELS_PER_UNIT * 10, anchorY +  PIXELS_PER_UNIT * 6, PIXELS_PER_UNIT * 8 ,  PIXELS_PER_UNIT * 2 ); //I6
		boundary[27] = new Rectangle(anchorX + PIXELS_PER_UNIT * 13, anchorY +  PIXELS_PER_UNIT * 6, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I6
		boundary[28] = new Rectangle(anchorX + PIXELS_PER_UNIT * 19, anchorY +  PIXELS_PER_UNIT * 6, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 8 ); //I1
		boundary[29] = new Rectangle(anchorX + PIXELS_PER_UNIT * 16, anchorY + PIXELS_PER_UNIT * 9, PIXELS_PER_UNIT * 5 ,  PIXELS_PER_UNIT * 2 ); //I2
		boundary[30] = new Rectangle(anchorX + PIXELS_PER_UNIT * 22, anchorY +  PIXELS_PER_UNIT * 6, PIXELS_PER_UNIT * 4 ,  PIXELS_PER_UNIT * 2 ); //I3
		boundary[31] = new Rectangle(anchorX + PIXELS_PER_UNIT * 7, anchorY + PIXELS_PER_UNIT * 15, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I14
		boundary[32] = new Rectangle(anchorX + PIXELS_PER_UNIT * 10, anchorY + PIXELS_PER_UNIT * 12, PIXELS_PER_UNIT * 3 ,  PIXELS_PER_UNIT * 1 ); //I35
		boundary[33] = new Rectangle(anchorX + PIXELS_PER_UNIT * 13, anchorY + PIXELS_PER_UNIT * 12, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 1 ); //I34
		boundary[34] = new Rectangle(anchorX + PIXELS_PER_UNIT * 15, anchorY + PIXELS_PER_UNIT * 12, PIXELS_PER_UNIT * 3 ,  PIXELS_PER_UNIT * 1 ); //I33
		boundary[35] = new Rectangle(anchorX + PIXELS_PER_UNIT * 10, anchorY + PIXELS_PER_UNIT * 12, PIXELS_PER_UNIT * 1 ,  PIXELS_PER_UNIT * 5 ); //I36
		boundary[36] = new Rectangle(anchorX + PIXELS_PER_UNIT * 10, anchorY + PIXELS_PER_UNIT * 16, PIXELS_PER_UNIT * 8 ,  PIXELS_PER_UNIT * 1 ); //I37
		boundary[37] = new Rectangle(anchorX + PIXELS_PER_UNIT * 17, anchorY + PIXELS_PER_UNIT * 12, PIXELS_PER_UNIT * 1 ,  PIXELS_PER_UNIT * 5 ); //I32
		boundary[38] = new Rectangle(anchorX + PIXELS_PER_UNIT * 19, anchorY + PIXELS_PER_UNIT * 15, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I31
		boundary[39] = new Rectangle(anchorX + PIXELS_PER_UNIT * 10, anchorY + PIXELS_PER_UNIT * 18, PIXELS_PER_UNIT * 8 ,  PIXELS_PER_UNIT * 2 ); //I23
		boundary[40] = new Rectangle(anchorX + PIXELS_PER_UNIT * 13, anchorY + PIXELS_PER_UNIT * 18, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I24
		boundary[41] = new Rectangle(anchorX + PIXELS_PER_UNIT * 2, anchorY + PIXELS_PER_UNIT * 21, PIXELS_PER_UNIT * 4 ,  PIXELS_PER_UNIT * 2 ); //I15
		boundary[42] = new Rectangle(anchorX + PIXELS_PER_UNIT * 4, anchorY + PIXELS_PER_UNIT * 21, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I16
		boundary[43] = new Rectangle(anchorX + OUTER_WALL_MIDDLE_X_ADJUSTMENT, anchorY + PIXELS_PER_UNIT * 24, PIXELS_PER_UNIT * 3 - OUTER_WALL_MIDDLE_X_ADJUSTMENT,  PIXELS_PER_UNIT * 2 ); //I17 //Connects to Outer wall
		boundary[44] = new Rectangle(anchorX + PIXELS_PER_UNIT * 7, anchorY + PIXELS_PER_UNIT * 21, PIXELS_PER_UNIT * 5 ,  PIXELS_PER_UNIT * 2 ); //I20
		boundary[45] = new Rectangle(anchorX + PIXELS_PER_UNIT * 2, anchorY + PIXELS_PER_UNIT * 27, PIXELS_PER_UNIT * 10 ,  PIXELS_PER_UNIT * 2 ); //I18
		boundary[46] = new Rectangle(anchorX + PIXELS_PER_UNIT * 7, anchorY + PIXELS_PER_UNIT * 24, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I19
		boundary[47] = new Rectangle(anchorX + PIXELS_PER_UNIT * 10, anchorY + PIXELS_PER_UNIT * 24, PIXELS_PER_UNIT * 8 ,  PIXELS_PER_UNIT * 2 ); //I21
		boundary[48] = new Rectangle(anchorX + PIXELS_PER_UNIT * 13, anchorY + PIXELS_PER_UNIT * 24, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I22
		boundary[49] = new Rectangle(anchorX + PIXELS_PER_UNIT * 16, anchorY + PIXELS_PER_UNIT * 21, PIXELS_PER_UNIT * 5 ,  PIXELS_PER_UNIT * 2 ); //I25
		boundary[50] = new Rectangle(anchorX + PIXELS_PER_UNIT * 22, anchorY + PIXELS_PER_UNIT * 21, PIXELS_PER_UNIT * 4 ,  PIXELS_PER_UNIT * 2 ); //I30
		boundary[51] = new Rectangle(anchorX + PIXELS_PER_UNIT * 22, anchorY + PIXELS_PER_UNIT * 21, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I29
		boundary[52] = new Rectangle(anchorX + PIXELS_PER_UNIT * 25  , anchorY + PIXELS_PER_UNIT * 24, PIXELS_PER_UNIT * 2 + OUTLINE_WIDTH * 3 + OUTER_WALL_MIDDLE_X_ADJUSTMENT ,  PIXELS_PER_UNIT * 2 ); //I28  //Connects to Outer wall
		boundary[53] = new Rectangle(anchorX + PIXELS_PER_UNIT * 19, anchorY + PIXELS_PER_UNIT * 24, PIXELS_PER_UNIT * 2 ,  PIXELS_PER_UNIT * 5 ); //I27
		boundary[54] = new Rectangle(anchorX + PIXELS_PER_UNIT * 16, anchorY + PIXELS_PER_UNIT * 27, PIXELS_PER_UNIT * 10 ,  PIXELS_PER_UNIT * 2 ); //I26
	}
	
	public Collection<PlayableArea> getPlayableArea(){
		return playableArea;
	}
	
	public Collection<PlayableArea> getCorneringArea(){
		return pManCorneringArea;
	}
	
	public Collection<GhostHouseArea> getGhostHouseArea(){
		return ghostHouseArea;
	}
	
	public Collection<Wormhole> getWormholes(){
		return wormholes;
	}
	
	public ArrayList<ArrayList<ArrayList<GameElement>>> getDiscreteBoard(){
		return discreteBoard;
	}
	
	public Collection<MoveableElement> getMoveableElements(){
		return moveableElements;
	}
	
	public Collection<DynamicElement> getDynamicElements(){
		return dynamicElements;
	}
	
	public RectangularShape [] getBoundary(){
		return boundary;
	}

	public void move() {
		int[] next, cur;
		if(!pause){
			for(MoveableElement me: getMoveableElements()){
				cur = me.getCurrentDirection();
				next = me.getNextDirection(this);
				if(ActionUtil.simulateAction(me, this, next)){
					cur = next;
					me.setCurrentDirection(next);
				}
				else if(me.getEnum() == GameElementName.FAUXMAN){//TODO: Check if this is needed
					 me.setNextDirection(cur);
				}

				ActionUtil.executeAction(me,this, cur);
			}
			cleanupDynamicElement(ateDE);
		}
	}
	
	private void cleanupDynamicElement(DynamicElement de) {
		dynamicElements.remove(de);
		moveableElements.remove(de);
		ateDE = null;
	}

	public void setFauxmanDirection(int[] action){
		fMan.setNextAction(action);
	}
	
	public FauxMan getFauxman(){
		return fMan;
	}
	
	public BlinkyGhost getBlinky(){
		return blinky;
	}

	public void ateFood(Food de) {
		totalScore += FOOD_POINTS;
		ateDE = de;
	}

	public void ateBonus(Bonus de) {
		totalScore += BONUS_POINTS;
		ateDE = de;
	}
	
	public void hitAttackingGhost(Ghost de) {
		System.out.println("Game over");
		System.exit(0);
	}

	public  void hitPellet(Pellet de) {
		//totalScore += FOOD_POINTS;
		ateDE = de;
		
		//synchronized to lock down the PELLETS_ACTIVE variable
		//* From java, not possible for two invocations of synchronized methods on the same object to interleave.
		synchronized(this)
		{
			++PELLETS_ACTIVE;
		}
System.out.println("Pellet hit start");

		
		for(Ghost g: ghosts)
		{
System.out.println(g.getStates());

			g.changeState(VulnerableGhostState.getInstance());
		}
		vTm.schedule(new VulnerableGhostTask(this), VULNERABLE_TIME);
System.out.println("Pellet hit over");

	}

	public void hitVulnerableGhost(Ghost g) {
		totalScore += GHOST_POINTS;
		g.changeState(EatenGhostState.getInstance()); //TODO: need to finish
	}

	public void vulnerableGhostTimeExpired() {
		
		//synchronized to lock down the PELLETS_ACTIVE variable since there may be several timers
		//* From java, not possible for two invocations of synchronized methods on the same object to interleave. only synched up a block and not a method for performance reasons
		synchronized(this)
		{
			//If this is not the last pellet timer then dont change state
			if(--PELLETS_ACTIVE > 0)
				return;
		}
System.out.println("Vulnerable time over start");
		
		setGhostsToCurrentState(true);
System.out.println("Vulnerable time over start");

	}

	public void leavingHouseTimeExpired(){
		for(Ghost g : ghosts){
			g.setLeavingTimer(true);
		}
	}
	
	public void togglePauseGame() {
		pause = !pause;
	}

	public void currentGhostModeExpired() {
		ghostStateScatter = !ghostStateScatter;
		modeNumber++;
System.out.println("current Mode: "+ modeNumber+", currentModeSeconds: "+getCurrentModeSeconds());
		setGhostsToCurrentState(false);
		updateHouseChangeStateCounter();
		vTm.schedule(new ModeChangeTask(this),  getCurrentModeSeconds());
	}
	
	private void setGhostsToCurrentState(boolean pelletExpired){
		GhostState state;
		for(Ghost g: ghosts)
		{
			if(ghostStateScatter){
				state = g.getScatterState();
			}else{
				state = g.getChaseState();
			}
			
			if(pelletExpired){
				g.pelletExpired(state);
			}else{
				g.changeState(state);
			}
		}
	}
	
	private void updateHouseChangeStateCounter(){
		for(Ghost g: ghosts)
		{
			for(GhostState gs: g.getStates())
			{
				if(gs.getStateType() == StateType.GHOST_HOUSE_STATE)
					g.setInHouseStateChanges(g.getInHouseStateChanges()+1);
			}
		}
	}
	
	public GhostState getGhostsCurrentState(Ghost g){
		if(ghostStateScatter)
			return g.getScatterState();
		else
			return g.getChaseState();
	}
	
	public long getCurrentModeSeconds(){
		return modeSeconds[modeNumber];
	}



}

/******************************************************************
*	TASK LIST:
*	- Chunnel  which we will do by making each character a thread and sleep them 
*	- All other time based oddities ( ghost house timer, multiple lives, Levels)
*	- State defects
*		+ Check Scatter state after eating pellet (fauxman passes through ghosts)
*	- Ghost House Area, Clyde's pixel defect changing from Entering to Purgatory
******************************************************************/

/******************************************************************
*	CURRENT TASK:
*   - No up defect, ghosts cant get out of their house if last state was Purgatory Up
*	- Chunnel
******************************************************************/
